#!/usr/bin/env perl

use strict;
use warnings;
use 5.010;

use Encode 'decode';

use Utils::WebUtils;
use Utils::Constants;

use Native::DataCollector;

use Service::HomeEnvironmentService;
use Service::LifeAssistantService;
use Service::SystemCenterService;
use Service::CloudService;

use Mojolicious::Lite;
use Mojo::Util qw(url_escape url_unescape);

my $log = Mojo::Log->new;

# Init the privileges map in cache
Service::SystemCenterService::load_privileges_map();

under sub {
    my $c = shift;
    my $route_name = $c->match->endpoint->name;

    # User login doesn't need authentication
    if ($route_name eq 'userlogin') {
        $log->debug("Route $route_name doesn't need authentication.");
        return 1;
    }

    # Get user info by Access token, and start authentication
    my $access_token = $c->req->headers->header('Access-Token') || '';
    my %user = Service::SystemCenterService::get_login_user_info($access_token);
    
    if (%user) {
        my $current_user_id = $user{user_id};
        my $current_user_role = $user{user_role};

        # check if the route is accessible for current role
        unless (Service::SystemCenterService::is_accessible_by_this_role($route_name, $current_user_role)) {
            $c->render(text => get_result_code_json(Utils::Constants::RESULT_CODE_INSUFFICIENT_PRIVILEGE_ERROR));

            $log->debug("$route_name is not accessible for role $current_user_role");
            return undef;
        }

        $c->param(_SESSION_USER_ID => $current_user_id);
        $c->param(_SESSION_USER_ROLE => $current_user_role);

        $log->debug("Access permitted for token: $access_token, whose id is ".$user{user_id});
        return 1;
    }

    # User isn't authenticated
    $c->render(text => get_result_code_json(Utils::Constants::RESULT_CODE_AUTHENTICATION_FAILURE));
    $log->debug("Access denied for token: $access_token");
    return undef;
};

group {

    under '/sweet_home';

    # My Cloud Controllers
    get '/myfiles/list' => sub {
        my $c = shift;
        my $path = $c->param('path');
        my $my_id = $c->param('_SESSION_USER_ID');

        $path =~ s/^\///g;
        my @files = Service::CloudService::list_files_in_dir("$my_id", "$path");

        $c->render(text => convert_to_json(\@files));
    };

    get '/myfiles/show' => sub {
        my $c = shift;
        my $path = $c->param('path');
        my $my_id = $c->param('_SESSION_USER_ID');

        $path =~ s/^\///g;

        my $file = Service::CloudService::get_file_by_user_and_path("$my_id", "$path");
        my $filename = Utils::WebUtils::extract_filename_from_path("$path") || '';

        $filename =~ s/ /_/g; 
        $filename = url_escape $filename;
        $log->debug("Download File: $filename");

        if ($file) {
            $c->res->headers->content_disposition("attachment;filename=$filename;");
            $c->res->headers->content_type('application/octet-stream');
            $c->res->content->asset($file);
        }

        $c->rendered(200);
    };
    
    post '/myfiles/delete' => sub {
        my $c = shift;
        my $path = $c->param('path');
        my $my_id = $c->param('_SESSION_USER_ID');

        $path =~ s/^\///g;
        my $is_success = Service::CloudService::remove_file_by_user_and_path("$my_id", "$path");
        my $result_code = !$is_success ? 0 : 1;

        $c->render(text => get_result_code_json($result_code));
    };

    post '/myfiles/rename' => sub {
        my $c = shift;
        my $ori_path = $c->param('ori_path');
        my $new_path = $c->param('new_path');
        my $my_id = $c->param('_SESSION_USER_ID');

        $ori_path =~ s/^\///g;
        $new_path =~ s/^\///g;
        my $is_success = Service::CloudService::rename_file_by_user_and_path("$my_id", "$ori_path", "$new_path");
        my $result_code = !$is_success ? 0 : 1;

        $c->render(text => get_result_code_json($result_code));
    };

    post '/myfiles/new' => sub {
        my $c = shift;
        my $file = $c->param('file');
        my $path = $c->param('path');
        my $my_id = $c->param('_SESSION_USER_ID');
        my $filename = decode('utf8', url_unescape($file->filename));
        
        $log->debug("Get file: ".$filename."  path: $path");

        # Check file size
        return $c->render(text => get_result_code_json(Utils::Constants::RESULT_CODE_FILE_TOO_LARGE_ERROR), status => 200)
            if $c->req->is_limit_exceeded;
        
        # Check file if undef
        return $c->render(text => get_result_code_json(Utils::Constants::RESULT_CODE_INVALID_FILE), status => 200) 
            unless $file or $path;

        my $is_success = Service::CloudService::add_new_file($my_id, $path, $filename, $file);

        if (!$is_success) {
            return $c->render(text => get_result_code_json(Utils::Constants::RESULT_CODE_INVALID_FILE)); 
        }

        return $c->render(text => get_result_code_json(1));
    };

    post '/myfiles/folder/new' => sub {
        my $c = shift;
        my $dir_name = Utils::WebUtils::trim($c->param('name'));
        my $path = $c->param('path');
        my $my_id = $c->param('_SESSION_USER_ID');

        $path =~ s/^\///g;
        my $is_success = Service::CloudService::create_new_folder_in_specified_path($my_id, $path, $dir_name);

        if (!$is_success) {
            return $c->render(text => get_result_code_json(Utils::Constants::RESULT_CODE_FOLDER_CREATION_ERROR)); 
        }

        return $c->render(text => get_result_code_json(1));
    };

    # Home Environment Controllers
    get '/temperature/data.json' => sub {
        my $c = shift;
        my $view = $c->param('view');
        my @data;
        my %result;

        if ($view eq 'raw') {
            @data = Service::HomeEnvironmentService::get_room_temperature_list();
        }
        elsif ($view eq 'graph') {
            my $resultCode = Service::HomeEnvironmentService::generate_temperature_linechart_picture();

            %result = (
                graph => ($resultCode == 1 ? Utils::Constants::TEMPERATURE_STATS_PICTURE : ""),
            );
            push @data,\%result;
        }

        $c->render(text => convert_to_json(\@data));
    };

    get '/brightness/data.json' => sub {
        my $c = shift;
        my @data = Service::HomeEnvironmentService::get_room_brightness_list();

        $c->render(text => convert_to_json(\@data));
    };

    get '/firesafety/data.json' => sub {
        my $c = shift;
        my @data = Service::HomeEnvironmentService::get_room_firesafety_list();

        $c->render(text => convert_to_json(\@data));
    };

    get '/environment/data.json' => sub {
        my $c = shift;
        my @data = Service::HomeEnvironmentService::get_current_home_environment();

        $c->render(text => convert_to_json(\@data));
    };

    # Life Assistant Controllers
    get '/housework/data.json' => sub {
        my $c = shift;
        my $user_id = $c->param('user');

        if ($user_id !~ Utils::Constants::REG_TEST_NUMBER) {
            $c->render(text => get_result_code_json(Utils::Constants::RESULT_CODE_PARAM_TYPE_ERROR));
            return;
        }

        if ($user_id == 0) {
            $user_id = undef;
        }

        my @data = Service::LifeAssistantService::get_housework_for_user($user_id);

        $c->render(text => convert_to_json(\@data));
    };

    get '/housework/:id' => [id => qr/\d+/]  => sub {
        my $c = shift;
        my $housework_id = $c->param('id');
        my @data = Service::LifeAssistantService::get_specified_housework_detail($housework_id);

        $c->render(text => convert_to_json(\@data));
    };

    post '/housework/new' => sub {
        my $c = shift;
        my $user_id = $c->param('user');
        my $task = Utils::WebUtils::trim($c->param('task'));
        my $reward = Utils::WebUtils::trim($c->param('reward'));

        if ($user_id !~ Utils::Constants::REG_TEST_NUMBER) {
            $c->render(text => get_result_code_json(Utils::Constants::RESULT_CODE_PARAM_TYPE_ERROR));
            return;
        }

        my $result = Service::LifeAssistantService::add_new_housework($user_id, $task, $reward);

        $c->render(text => get_result_code_json($result));
    };

    post '/housework/update' => sub {
        my $c = shift;
        my $housework_id = $c->param('id');

        if ($housework_id !~ Utils::Constants::REG_TEST_NUMBER) {
            $c->render(text => get_result_code_json(Utils::Constants::RESULT_CODE_PARAM_TYPE_ERROR));
            return;
        }
        
        my $result = Service::LifeAssistantService::update_housework_status($housework_id);

        $c->render(text => get_result_code_json($result));
    };

    get '/bloodsugar/data.json' => sub {
        my $c = shift;
        my $user_id = $c->param('_SESSION_USER_ID');

        if ($user_id !~ Utils::Constants::REG_TEST_NUMBER) {
            $c->render(text => get_result_code_json(Utils::Constants::RESULT_CODE_PARAM_TYPE_ERROR));
            return;
        }

        my @data = Service::LifeAssistantService::get_bloodsugar_history_list($user_id);

        $c->render(text => convert_to_json(\@data));
    };

    get '/bloodsugar/stats.json' => sub {
        my $c = shift;
        my $user_id = $c->param('_SESSION_USER_ID');
        my %result; 
        my @data;
        
        if ($user_id !~ Utils::Constants::REG_TEST_NUMBER) {
            $c->render(text => get_result_code_json(Utils::Constants::RESULT_CODE_PARAM_TYPE_ERROR));
            return;
        }

        %result = (
            graph => Service::LifeAssistantService::get_today_bloodsugar_stats($user_id),
        );
        
        push @data, \%result;

        $c->render(text => convert_to_json(\@data));
    };

    post '/bloodsugar/new' => sub {
        my $c = shift;
        my $user_id = $c->param('_SESSION_USER_ID');
        my $date = $c->param('date');
        my $time = $c->param('time');
        my $tag = $c->param('tag');
        my $value = Utils::WebUtils::trim($c->param('value'));

        if ($user_id !~ Utils::Constants::REG_TEST_NUMBER
            or $date !~ Utils::Constants::REG_TEST_DATE
            or $time !~ Utils::Constants::REG_TEST_TIME_NO_SECOND
            or $tag !~ Utils::Constants::REG_TEST_NUMBER
            or $value !~ Utils::Constants::REG_TEST_REAL_NUMBER) {
            $c->render(text => get_result_code_json(Utils::Constants::RESULT_CODE_PARAM_TYPE_ERROR));
            return;
        }

        my $result = Service::LifeAssistantService::add_new_bloodsugar(
                    $user_id, $date, $time, $tag, $value
                 );
        
        $c->render(text => get_result_code_json($result));
    };

    post '/contactsync/backup' => sub {
        my $c = shift;
        my $contacts = $c->param('contacts.json');
        my $my_id = $c->param('_SESSION_USER_ID');
        my $result = Service::LifeAssistantService::save_contact_by_user($my_id, $contacts);

        $c->render(text => get_result_code_json($result));
    };

    get '/contactsync/sync' => sub {
        my $c = shift;
        my $my_id = $c->param('_SESSION_USER_ID');
        my @data = Service::LifeAssistantService::get_contacts_by_user($my_id);

        $c->render(text => convert_to_json(\@data));
    };

    post '/locationtrack/new' => sub {
        my $c = shift;
        my $user_id = $c->param('_SESSION_USER_ID');
        my $geolocation = $c->param('geolocation');
        my $is_living_in_china = $c->param('is_living_in_china');

        if ($is_living_in_china !~ Utils::Constants::REG_TEST_NUMBER 
            or $geolocation !~ Utils::Constants::REG_TEST_GEOLOCATION) {
            $c->render(text => get_result_code_json(Utils::Constants::RESULT_CODE_PARAM_TYPE_ERROR));
            return;
        }

        my $location_desc = Service::LifeAssistantService::get_location_address_description($is_living_in_china, $geolocation);
        my $result = Service::LifeAssistantService::add_new_location($location_desc, $user_id);
        
        $c->render(text => get_result_code_json($result));
    };

    get '/locationtrack/:user' => [user => qr/\d+/] => sub {
        my $c = shift;
        my $user_id = $c->param('user');
        my @data = Service::LifeAssistantService::get_location_track_by_user($user_id);

        $c->render(text => convert_to_json(\@data));
    };

    get '/locationtrack/now' => sub {
        my $c = shift;
        my @data = Service::LifeAssistantService::get_all_users_location_today();

        $c->render(text => convert_to_json(\@data));
    };

    # System Center Controllers
    get '/pihealth/data.json' => sub {
        my $c = shift;
        my @data = Service::SystemCenterService::get_current_pi_environment();

        $c->render(text => convert_to_json(\@data));
    };

    get '/user/data.json' => sub {
        my $c = shift;
        my @data = Service::SystemCenterService::get_all_users_list();

        $c->render(text => convert_to_json(\@data));
    };

    get '/user/me' => sub {
        my $c = shift;
        # Get user id of the request from session
        my $my_id = $c->param('_SESSION_USER_ID');
        my @data = Service::SystemCenterService::get_user_by_id($my_id);
        
        $c->render(text => convert_to_json(\@data));
    };

    post '/user/add' => sub {
        my $c = shift;
        my $my_role = $c->param('_SESSION_USER_ROLE');
        my $new_user_name = $c->param('username');
        my $new_user_role = $c->param('role');

        if (Utils::WebUtils::is_empty($new_user_name) or 
                $new_user_role !~ Utils::Constants::REG_TEST_NUMBER) {
            $c->render(text => get_result_code_json(Utils::Constants::RESULT_CODE_PARAM_TYPE_ERROR));        
            return;
        }
        
        my $result_code = Service::SystemCenterService::add_new_user($new_user_name, $new_user_role);

        $c->render(text => get_result_code_json($result_code));
    };

    post '/user/delete' => sub {
        my $c = shift;
        my $id = $c->param('id');
        my $my_role = $c->param('_SESSION_USER_ROLE');

        unless ($id =~ Utils::Constants::REG_TEST_NUMBER) {
            $c->render(text => get_result_code_json(Utils::Constants::RESULT_CODE_PARAM_TYPE_ERROR));      
            return;
        }

        my $result_code = Service::SystemCenterService::delete_user($id);

        $c->render(text => get_result_code_json($result_code));
    };

    post '/user/password/reset' => sub {
        my $c = shift;
        my $my_role = $c->param('_SESSION_USER_ROLE');
        my $id = $c->param('id');

        unless ($id =~ Utils::Constants::REG_TEST_NUMBER) {
            $c->render(text => get_result_code_json(Utils::Constants::RESULT_CODE_PARAM_TYPE_ERROR));      
            return;
        }
        
        my $result_code = Service::SystemCenterService::reset_user_password($id);

        $c->render(text => get_result_code_json($result_code));
    };

    post '/user/edit' => sub {
        my $c = shift;
        my $my_id = $c->param('_SESSION_USER_ID');
        my $fields_to_edit = $c->param('new_fields_values_pairs');
        $log->debug("Fields: $fields_to_edit");
        my $user_fields_map = json_to_hash($fields_to_edit);
        my $result_code = Service::SystemCenterService::update_user_by_map($my_id, $user_fields_map);
        
        $c->render(text => get_result_code_json($result_code));
    };

    post '/user/password/edit' => sub {
        my $c = shift;
        my $my_id = $c->param('_SESSION_USER_ID');
        my $current_password = $c->param('current_password');
        my $new_password = $c->param('new_password');

        if (Utils::WebUtils::is_empty($current_password) or 
                Utils::WebUtils::is_empty($new_password)) {
            $c->render(text => get_result_code_json(Utils::Constants::RESULT_CODE_PARAM_TYPE_ERROR));
            return;
        }

        my $result_code = Service::SystemCenterService::update_user_password($my_id, $current_password, $new_password);
        $c->render(text => get_result_code_json($result_code));
    };

    post '/user/login' => sub {
        my $c = shift;
        my $name = $c->param('username');
        my $password = $c->param('password');
        
        if (!defined $name or !defined $password) {
            $c->render(text => get_result_code_json(Utils::Constants::RESULT_CODE_PARAM_TYPE_ERROR));
            return;
        }

        my %check_result = Service::SystemCenterService::check_user_existance($name, $password);
        my $result_code = $check_result{is_authenticated};
        my $access_token = $check_result{access_token};
        my $user = $check_result{user};

        my @data; 
        my %result = (
                        token => $access_token,
                        resultCode => $result_code,
                        user => $user,
        );
        
        push @data, \%result;

        $c->render(text => convert_to_json(\@data));
    };

    post '/user/photo/edit' => sub {
        my $c = shift;
        my $my_id = $c->param('_SESSION_USER_ID');
        my $file = $c->param('file');
        
        $log->debug("Get file: ".$file->filename);

        # Check file size
        return $c->render(text => get_result_code_json(Utils::Constants::RESULT_CODE_FILE_TOO_LARGE_ERROR), status => 200)
            if $c->req->is_limit_exceeded;
        
        # Check file if undef
        return $c->render(text => get_result_code_json(Utils::Constants::RESULT_CODE_INVALID_FILE), status => 200) 
            unless $file;

        my $filename_to_save = Utils::WebUtils::create_uuid().$file->filename;
        my $is_success = Service::CloudService::save_image_file_to_static($file, $filename_to_save);

        if (!$is_success) {
            return $c->render(text => get_result_code_json(Utils::Constants::RESULT_CODE_INVALID_FILE)); 
        }

        my %user_avatar_field_value_map = (avatar => $filename_to_save);
        my $result_code = Service::SystemCenterService::update_user_by_map($my_id, \%user_avatar_field_value_map);

        return $c->render(text => get_result_code_json($result_code));
    };

    post '/option/edit' => sub {
        my $c = shift;
        my $my_id = $c->param('_SESSION_USER_ID');
        my $key = $c->param('key');
        my $value = $c->param('value');
        
        if (Utils::WebUtils::is_empty($key) or Utils::WebUtils::is_empty($value)) {
            $c->render(text => get_result_code_json(Utils::Constants::RESULT_CODE_PARAM_TYPE_ERROR));
        }

        my $result = Service::SystemCenterService::update_user_option($my_id, $key, $value);
        $c->render(text => get_result_code_json($result));
    };

    get '/options' => sub {
        my $c = shift;
        my $my_id = $c->param('_SESSION_USER_ID');
        my @data = Service::SystemCenterService::get_options_for_user($my_id);

        $c->render(text => convert_to_json(\@data));
    };

};

app->start;
